home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2009 February / PCWFEB09.iso / Software / Resources / Audio, Video & Photo / Songbird 0.7.0 / Songbird_0.7.0_windows-i686-msvc8.exe / components / sbServicePaneService.js < prev    next >
Text File  |  2008-08-06  |  42KB  |  1,300 lines

  1. /** vim: ts=2 sw=2 expandtab
  2. //
  3. // BEGIN SONGBIRD GPL
  4. //
  5. // This file is part of the Songbird web player.
  6. //
  7. // Copyright(c) 2005-2008 POTI, Inc.
  8. // http://songbirdnest.com
  9. //
  10. // This file may be licensed under the terms of of the
  11. // GNU General Public License Version 2 (the "GPL").
  12. //
  13. // Software distributed under the License is distributed
  14. // on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either
  15. // express or implied. See the GPL for the specific language
  16. // governing rights and limitations.
  17. //
  18. // You should have received a copy of the GPL along with this
  19. // program. If not, go to http://www.gnu.org/licenses/gpl.html
  20. // or write to the Free Software Foundation, Inc.,
  21. // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  22. //
  23. // END SONGBIRD GPL
  24. //
  25.  */
  26.  
  27. /**
  28.  * \file ServicePaneService.js
  29.  * \brief the service pane service manages the tree behind the service pane
  30.  *
  31.  * TODO:
  32.  *  - implement lazy saving of dirty datasources.
  33.  *  - handle module removal
  34.  *
  35.  */
  36.  
  37. function DEBUG(msg) {
  38.   //dump('sbServicePaneService.js: '+msg+'\n');
  39. }
  40.  
  41. const Cc = Components.classes;
  42. const Ci = Components.interfaces;
  43. const Cr = Components.results;
  44. const Ce = Components.Exception;
  45.  
  46. const RDFSVC = Cc['@mozilla.org/rdf/rdf-service;1'].getService(Ci.nsIRDFService);
  47. const RDFCU = Cc['@mozilla.org/rdf/container-utils;1'].getService(Ci.nsIRDFContainerUtils);
  48. const IOSVC = Cc['@mozilla.org/network/io-service;1'].getService(Ci.nsIIOService);
  49. const gPrefs = Cc['@mozilla.org/preferences-service;1'].getService(Ci.nsIPrefBranch);
  50.  
  51. const NC='http://home.netscape.com/NC-rdf#';
  52. const SP='http://songbirdnest.com/rdf/servicepane#';
  53.  
  54. const STRINGBUNDLE='chrome://songbird/locale/songbird.properties';
  55.  
  56. function ServicePaneNode (ds, resource) {
  57.   this._resource = resource;
  58.  
  59.   this._dataSource = ds;
  60.   this._dataSource.QueryInterface(Ci.nsIRDFRemoteDataSource);
  61.  
  62.   // set up this._container if this is a Seq
  63.   if (RDFCU.IsSeq(ds, resource)) {
  64.     this._container = Cc['@mozilla.org/rdf/container;1'].createInstance(Ci.nsIRDFContainer);
  65.     this._container.Init(ds, resource);
  66.     this._resource = null;
  67.   } else {
  68.     this._container = null;
  69.   }
  70. }
  71.  
  72. ServicePaneNode.prototype.QueryInterface = function (iid) {
  73.   if (!iid.equals(Ci.sbIServicePaneNode) &&
  74.     !iid.equals(Ci.sbIServicePaneNodeInternal) &&
  75.     !iid.equals(Ci.nsIClassInfo) &&
  76.     !iid.equals(Ci.nsISupports)) {
  77.     throw Components.results.NS_ERROR_NO_INTERFACE;
  78.   }
  79.   return this;
  80. }
  81. ServicePaneNode.prototype.classDescription = 'Songbird Service Pane Node';
  82. ServicePaneNode.prototype.classID = null;
  83. ServicePaneNode.prototype.contractID = null;
  84. ServicePaneNode.prototype.flags = Ci.nsIClassInfo.MAIN_THREAD_ONLY;
  85. ServicePaneNode.prototype.implementationLanguage = Ci.nsIProgrammingLanguage.JAVASCRIPT;
  86. ServicePaneNode.prototype.getHelperForLanguage = function(aLanguage) { return null; }
  87. ServicePaneNode.prototype.getInterfaces = function(count) {
  88.   var interfaces = [Ci.sbIServicePaneNode,
  89.             Ci.sbIServicePaneNodeInternal,
  90.             Ci.nsIClassInfo,
  91.             Ci.nsISupports,
  92.            ];
  93.   count.value = interfaces.length;
  94.   return interfaces;
  95. }
  96.  
  97. ServicePaneNode.prototype.__defineGetter__ ('resource',
  98.   function () {
  99.     if (this._container) {
  100.       return this._container.Resource;
  101.     }
  102.     else {
  103.       return this._resource;
  104.     }
  105.   }
  106. );
  107.  
  108. ServicePaneNode.prototype.__defineGetter__ ('id',
  109.     function () { return this.resource.ValueUTF8; });
  110.  
  111. ServicePaneNode.prototype.__defineGetter__ ('isContainer',
  112.     function () { return this._container != null });
  113.  
  114. ServicePaneNode.prototype.setAttributeNS = function (aNamespace, aName, aValue) {
  115.   var property = RDFSVC.GetResource(aNamespace+aName);
  116.   var target = this._dataSource.GetTarget(this.resource, property, true);
  117.   if (target) {
  118.     this._dataSource.Unassert(this.resource, property, target);
  119.   }
  120.   if (aValue != null) {
  121.     this._dataSource.Assert(this.resource, property,
  122.                 RDFSVC.GetLiteral(aValue), true);
  123.   }
  124. }
  125.  
  126. ServicePaneNode.prototype.getAttributeNS = function (aNamespace, aName) {
  127.   var property = RDFSVC.GetResource(aNamespace+aName);
  128.   var target = this._dataSource.GetTarget(this.resource, property, true);
  129.   if (target) {
  130.     var value = target.QueryInterface(Ci.nsIRDFLiteral).Value
  131.     return value;
  132.   } else {
  133.     return null;
  134.   }
  135. }
  136.  
  137. ServicePaneNode.prototype.hasAttributeNS =
  138.   function SPN_hasAttributeNS(aNamespace, aName)
  139. {
  140.   var property = RDFSVC.GetResource(aNamespace+aName);
  141.   return this._dataSource.hasArcOut(this.resource, property);
  142. }
  143.  
  144. ServicePaneNode.prototype.__defineGetter__ ('url', function () {
  145.   return this.getAttributeNS(NC,'URL'); })
  146. ServicePaneNode.prototype.__defineSetter__ ('url', function (aValue) {
  147.   this.setAttributeNS(NC,'URL', aValue); });
  148.  
  149. ServicePaneNode.prototype.__defineGetter__ ('image', function () {
  150.   return this.getAttributeNS(NC,'Icon'); })
  151. ServicePaneNode.prototype.__defineSetter__ ('image', function (aValue) {
  152.   this.setAttributeNS(NC,'Icon', aValue); });
  153.  
  154. ServicePaneNode.prototype.__defineGetter__ ('name', function () {
  155.   return this.getAttributeNS(NC,'Name'); })
  156. ServicePaneNode.prototype.__defineSetter__ ('name', function (aValue) {
  157.   this.setAttributeNS(NC,'Name', aValue); });
  158.  
  159. ServicePaneNode.prototype.__defineGetter__ ('tooltip', function () {
  160.   return this.getAttributeNS(NC,'Description'); })
  161. ServicePaneNode.prototype.__defineSetter__ ('tooltip', function (aValue) {
  162.   this.setAttributeNS(NC,'Description', aValue); });
  163.  
  164. ServicePaneNode.prototype.__defineGetter__ ('properties', function () {
  165.   return this.getAttributeNS(SP,'Properties'); })
  166. ServicePaneNode.prototype.__defineSetter__ ('properties', function (aValue) {
  167.  this.setAttributeNS(SP,'Properties', aValue); });
  168.  
  169. ServicePaneNode.prototype.__defineGetter__ ('contractid', function () {
  170.   return this.getAttributeNS(SP,'contractid'); })
  171. ServicePaneNode.prototype.__defineSetter__ ('contractid', function (aValue) {
  172.   this.setAttributeNS(SP,'contractid', aValue); });
  173.  
  174. ServicePaneNode.prototype.__defineGetter__ ('hidden', function () {
  175.   return this.getAttributeNS(SP,'Hidden') == 'true'; })
  176. ServicePaneNode.prototype.__defineSetter__ ('hidden', function (aValue) {
  177.   this.setAttributeNS(SP,'Hidden', aValue?'true':'false'); });
  178.  
  179. ServicePaneNode.prototype.__defineGetter__ ('editable', function () {
  180.   return this.getAttributeNS(SP,'Editable') == 'true'; })
  181. ServicePaneNode.prototype.__defineSetter__ ('editable', function (aValue) {
  182.   this.setAttributeNS(SP,'Editable', aValue?'true':'false'); });
  183.  
  184. ServicePaneNode.prototype.__defineGetter__ ('isOpen', function () {
  185.   return this.getAttributeNS(SP,'Open') == 'true'; })
  186. ServicePaneNode.prototype.__defineSetter__ ('isOpen', function (aValue) {
  187.   this.setAttributeNS(SP,'Open', aValue?'true':'false'); });
  188.  
  189. ServicePaneNode.prototype.__defineGetter__ ('dndDragTypes', function () {
  190.   return this.getAttributeNS(SP,'dndDragTypes'); })
  191. ServicePaneNode.prototype.__defineSetter__ ('dndDragTypes', function (aValue) {
  192.   this.setAttributeNS(SP,'dndDragTypes', aValue); });
  193.  
  194. ServicePaneNode.prototype.__defineGetter__ ('dndAcceptNear', function () {
  195.   return this.getAttributeNS(SP,'dndAcceptNear'); })
  196. ServicePaneNode.prototype.__defineSetter__ ('dndAcceptNear', function (aValue) {
  197.   this.setAttributeNS(SP,'dndAcceptNear', aValue); });
  198.  
  199. ServicePaneNode.prototype.__defineGetter__ ('dndAcceptIn', function () {
  200.   return this.getAttributeNS(SP,'dndAcceptIn'); })
  201. ServicePaneNode.prototype.__defineSetter__ ('dndAcceptIn', function (aValue) {
  202.   this.setAttributeNS(SP,'dndAcceptIn', aValue); });
  203.  
  204. ServicePaneNode.prototype.__defineGetter__ ('stringbundle', function () {
  205.   return this.getAttributeNS(SP,'stringbundle'); })
  206. ServicePaneNode.prototype.__defineSetter__ ('stringbundle', function (aValue) {
  207.   this.setAttributeNS(SP,'stringbundle', aValue); });
  208.  
  209.  
  210. ServicePaneNode.prototype.__defineGetter__('childNodes',
  211.     function() {
  212.       if (!this.isContainer) {
  213.         throw this.id+' is not a container';
  214.       }
  215.       var e = this._container.GetElements();
  216.       var ds = this._dataSource;
  217.       return {
  218.         hasMoreElements: function() { return e.hasMoreElements(); },
  219.         getNext: function() {
  220.           if (!e.hasMoreElements()) { return null; }
  221.           var resource = e.getNext();
  222.           if (!resource) { return null; }
  223.           return new ServicePaneNode(ds,
  224.               resource.QueryInterface(Ci.nsIRDFResource));
  225.         }
  226.       };
  227.     });
  228.  
  229. ServicePaneNode.prototype.__defineGetter__('firstChild',
  230.     function () {
  231.       return this.childNodes.getNext();
  232.     });
  233.  
  234. ServicePaneNode.prototype.__defineGetter__('lastChild',
  235.     function () {
  236.       var enumerator = this.childNodes;
  237.       var node = null;
  238.       while (enumerator.hasMoreElements()) {
  239.         node = enumerator.getNext();
  240.       }
  241.       return node;
  242.     });
  243.  
  244. ServicePaneNode.prototype.__defineGetter__('nextSibling',
  245.     function () {
  246.       var parent = this.parentNode;
  247.       if (!parent) {
  248.         // can't have siblings without parents
  249.         return null;
  250.       }
  251.       var nodes = parent.childNodes;
  252.       while (nodes.hasMoreElements()) {
  253.         var node = nodes.getNext();
  254.         if (node.id == this.id) {
  255.           return nodes.getNext();
  256.         }
  257.       }
  258.       return null;
  259.     });
  260.  
  261. ServicePaneNode.prototype.__defineGetter__('parentNode',
  262.     function () {
  263.       var labels = this._dataSource.ArcLabelsIn(this.resource);
  264.       while (labels.hasMoreElements()) {
  265.         var label = labels.getNext().QueryInterface(Ci.nsIRDFResource);
  266.         if (RDFCU.IsOrdinalProperty(label)) {
  267.           return new ServicePaneNode(this._dataSource,
  268.               this._dataSource.GetSource(label, this.resource, true));
  269.         }
  270.       }
  271.       return null;
  272.     });
  273.  
  274. ServicePaneNode.prototype.__defineGetter__('previousSibling',
  275.     function () {
  276.       var parent = this.parentNode;
  277.       if (!parent) {
  278.         // can't have siblings without parents
  279.         return null;
  280.       }
  281.       var prev = null;
  282.       var nodes = parent.childNodes;
  283.       while (nodes.hasMoreElements()) {
  284.         var node = nodes.getNext();
  285.         if (node.id == this.id) {
  286.           return prev;
  287.         }
  288.         prev = node;
  289.       }
  290.       return null;
  291.     });
  292.  
  293. ServicePaneNode.prototype.appendChild = function(aChild) {
  294.   if (!this.isContainer) {
  295.     throw this.id+' is not a container';
  296.   }
  297.  
  298.   aChild.unlinkNode();
  299.  
  300.   this._container.AppendElement(aChild.resource);
  301.   return aChild;
  302. };
  303.  
  304. ServicePaneNode.prototype.insertBefore = function(aNewNode, aAdjacentNode) {
  305.   DEBUG('insertBefore('+aNewNode.id+', '+aAdjacentNode.id+')');
  306.  
  307.   if (!this.isContainer) {
  308.     throw this.id + ' is not a container';
  309.   }
  310.  
  311.   if (this.id != aAdjacentNode.parentNode.id) {
  312.     throw aAdjacentNode.id + ' is not in ' + this.id;
  313.   }
  314.  
  315.   // unlink the node we're moving from where it is currently
  316.   aNewNode.unlinkNode();
  317.  
  318.   // work out where it should go
  319.   var index = this._container.IndexOf(aAdjacentNode.resource);
  320.  
  321.   DEBUG(' index='+index);
  322.  
  323.   // add it back in there
  324.   this._container.InsertElementAt(aNewNode.resource, index, true);
  325. }
  326.  
  327. ServicePaneNode.prototype.removeChild = function(aChild) {
  328.   if (!this.isContainer) {
  329.     throw this.id + ' is not a container';
  330.   }
  331.  
  332.   if (this._container.IndexOf(aChild.resource)< 0) {
  333.     throw aChild.id + ' is not in ' + this.id;
  334.   }
  335.  
  336.   this._container.RemoveElement(aChild.resource, true);
  337.  
  338.   aChild.clearNode();
  339. }
  340.  
  341. ServicePaneNode.prototype.replaceChild = function(aNewNode, aOldNode) {
  342.   if (!this.isContainer) {
  343.     throw this.id + ' is not a container';
  344.   }
  345.  
  346.   var index = this._container.IndexOf(aOldNode.resource);
  347.   if (index < 0) {
  348.     throw aOldNode.id + ' is not in ' + this.id;
  349.   }
  350.  
  351.   this.insertBefore(aNewNode, aOldNode);
  352.   this.removeChild(aOldNode);
  353. }
  354.  
  355. ServicePaneNode.prototype.unlinkChild = function (aChild) {
  356.   // fail silently
  357.   if (this._container.IndexOf(aChild.resource) < 0) {
  358.     return;
  359.   }
  360.   this._container.RemoveElement(aChild.resource, true);
  361. }
  362.  
  363. ServicePaneNode.prototype.unlinkNode = function () {
  364.   var parent = this.parentNode;
  365.   if (parent) {
  366.     parent.unlinkChild(this);
  367.   }
  368. }
  369.  
  370. ServicePaneNode.prototype.clearNode = function () {
  371.   if (this.isContainer) {
  372.     // first we need to remove all our children from the graph
  373.     var children = this.childNodes;
  374.     while (children.hasMoreElements()) {
  375.       this.removeChild(children.getNext());
  376.     }
  377.   }
  378.  
  379.   // then we need to find all our outgoing arcs
  380.   var arcs = this._dataSource.ArcLabelsOut(this.resource);
  381.   while (arcs.hasMoreElements()) {
  382.     var arc = arcs.getNext().QueryInterface(Ci.nsIRDFResource);
  383.     // get the targets
  384.     var targets = this._dataSource.GetTargets(this.resource, arc, true);
  385.     while (targets.hasMoreElements()) {
  386.       var target = targets.getNext().QueryInterface(Ci.nsIRDFNode);
  387.       // and remove them
  388.       this._dataSource.Unassert(this.resource, arc, target);
  389.     }
  390.   }
  391. }
  392.  
  393.  
  394.  
  395.  
  396.  
  397. function ServicePaneService () {
  398.   DEBUG('ServicePaneService() starts');
  399.   this._initialized = false;
  400.   var obsSvc = Cc['@mozilla.org/observer-service;1']
  401.     .getService(Ci.nsIObserverService);
  402.   obsSvc.addObserver(this, 'quit-application', false);
  403.   DEBUG('ServicePaneService() ends');
  404. }
  405. ServicePaneService.prototype.observe = /* for nsIObserver */
  406. function ServicePaneService_observe(subject, topic, data) {
  407.   DEBUG('ServicePaneService.observe() begins');
  408.   if (topic == 'quit-application') {
  409.     var obsSvc = Cc['@mozilla.org/observer-service;1']
  410.       .getService(Ci.nsIObserverService);
  411.     obsSvc.removeObserver(this, 'quit-application');
  412.     this.shutdown();
  413.   }
  414.   DEBUG('ServicePaneService.observe() ends');
  415. }
  416. ServicePaneService.prototype.init = function ServicePaneService_init() {
  417.   DEBUG('ServicePaneService.init() begins');
  418.  
  419.   if (this._initialized) {
  420.     // already initialized
  421.     DEBUG('already initialized');
  422.     return;
  423.   }
  424.   this._initialized = true;
  425.  
  426.   var dirsvc = Cc['@mozilla.org/file/directory_service;1'].getService(Ci.nsIProperties);
  427.  
  428.   // get the profile directory
  429.   var bResult = {};
  430.   var path = dirsvc.get('ProfD', Ci.nsIFile, bResult);
  431.   path.QueryInterface(Ci.nsIFile);
  432.   // the file where we store our pane's state
  433.   path.append('service-pane.rdf');
  434.  
  435.   var uri = IOSVC.newFileURI(path);
  436.   // FIXME: does this handle non-latin paths on non-linux?
  437.  
  438.   this._dataSource = RDFSVC.GetDataSourceBlocking(uri.spec);
  439.   this._dataSourceWrapped = new dsTranslator(this._dataSource, this);
  440.   this._dsSaver = new dsSaver(this._dataSource, 30*1000); // try to save every thirty seconds
  441.  
  442.   // listen to changes in the datasource
  443.   this._dataSource.AddObserver(this);
  444.  
  445.   // track service pane listeners
  446.   this._listeners = [];
  447.  
  448.   // the root of the tree
  449.   this._root = this.getNode('SB:Root');
  450.   if (!this._root) {
  451.     // looks like we need to bootstrap the tree
  452.     // create the root node
  453.     this._dataSource.Assert(RDFSVC.GetResource('SB:Root'),
  454.                 RDFSVC.GetResource(SP+'Hidden'),
  455.                 RDFSVC.GetLiteral('true'), true);
  456.     // make it a sequence
  457.     RDFCU.MakeSeq(this._dataSource, RDFSVC.GetResource('SB:Root'));
  458.     this._root = this.getNode('SB:Root');
  459.     this._root.hidden = false;
  460.   }
  461.  
  462.   // okay, lets get all the keys
  463.   this._modules = [];
  464.   var module_keys = [];
  465.   var catMgr = Cc["@mozilla.org/categorymanager;1"]
  466.       .getService(Ci.nsICategoryManager);
  467.   var e = catMgr.enumerateCategory('service-pane');
  468.   while (e.hasMoreElements()) {
  469.     var key = e.getNext().QueryInterface(Ci.nsISupportsCString);
  470.     module_keys.push(key);
  471.   }
  472.  
  473.   // let's sort the list
  474.   module_keys.sort()
  475.  
  476.   // and lets create and init the modules in order
  477.   for(var i=0; i<module_keys.length; i++) {
  478.     var key = module_keys[i];
  479.     var contractid = catMgr.getCategoryEntry('service-pane', key);
  480.     DEBUG ('trying to load contractid: '+contractid);
  481.     DEBUG ('Cc[contractid]: '+Cc[contractid]);
  482.     try {
  483.       var module = Cc[contractid].getService(Ci.sbIServicePaneModule);
  484.       module.servicePaneInit(this);
  485.       this._modules.push(module);
  486.     } catch (e) {
  487.       DEBUG('ERROR CREATING SERVICE PANE MODULE: '+e);
  488.     }
  489.   }
  490.  
  491.   // HACK ALERT
  492.   // now, let's sort the top-level nodes by their weight.
  493.   // this idea of node weight is stolen from Drupal menus
  494.   var node = this._root.firstChild;
  495.   while (node) {
  496.     var value = parseInt(node.getAttributeNS(SP, 'Weight'));
  497.     while (node.previousSibling) {
  498.       var prev_value =
  499.           parseInt(node.previousSibling.getAttributeNS(SP, 'Weight'));
  500.       if (prev_value > value) {
  501.         this._root.insertBefore(node, node.previousSibling);
  502.       } else {
  503.         break;
  504.       }
  505.     }
  506.  
  507.     node = node.nextSibling;
  508.   }
  509.  
  510.   DEBUG('ServicePaneService.init() ends');
  511. }
  512.  
  513. ServicePaneService.prototype.shutdown = function ServicePaneService_shutdown() {
  514.   DEBUG('ServicePaneService.shutdown() begins');
  515.  
  516.   if (!this._initialized) {
  517.     // if we werent initialized, there's no need to shutdown
  518.     return;
  519.   }
  520.  
  521.   // Clear our array of service pane providers.  This is needed to break a
  522.   // reference cycle between the provider and this service
  523.   for (let i = 0; i < this._modules.length; i++) {
  524.     try {
  525.       this._modules[i].shutdown();
  526.     }
  527.     catch (e) {
  528.       // Ignore errors
  529.       Components.utils.reportError(e);
  530.     }
  531.     this._modules[i] = null;
  532.   }
  533.  
  534.   // remove ourselves as an RDF observer
  535.   this._dataSource.RemoveObserver(this);
  536.  
  537.   this._dataSource = null;
  538.   this._dataSourceWrapped = null;
  539.   this._root = null;
  540.   this._dsSaver = null;
  541.  
  542.   DEBUG('ServicePaneService.shutdown() ends');
  543. }
  544.  
  545. // for sbIServicePaneService.dataSource
  546. ServicePaneService.prototype.__defineGetter__('dataSource', function () {
  547.   if (!this._initialized) { this.init(); }
  548.   return this._dataSourceWrapped;
  549. });
  550.  
  551. // for sbIServicePaneService.root
  552. ServicePaneService.prototype.__defineGetter__('root', function () {
  553.   if (!this._initialized) { this.init(); }
  554.   return this._root;
  555. });
  556.  
  557. // FIXME: implement nsIClassInfo
  558. ServicePaneService.prototype.QueryInterface =
  559. function ServicePaneService_QueryInterface(iid) {
  560.   if (!iid.equals(Ci.sbIServicePaneService) &&
  561.     !iid.equals(Ci.nsIRDFObserver) &&
  562.     !iid.equals(Ci.nsIObserver) &&
  563.     !iid.equals(Ci.nsISupports)) {
  564.     throw Components.results.NS_ERROR_NO_INTERFACE;
  565.   }
  566.   return this;
  567. }
  568. ServicePaneService.prototype.addNode =
  569. function ServicePaneService_addNode(aId, aParent, aContainer) {
  570.   if (!this._initialized) { this.init(); }
  571.   DEBUG ('ServicePaneService.addNode('+aId+','+aParent+')');
  572.  
  573.   /* ensure the parent is supplied */
  574.   if (aParent == null) {
  575.     throw Ce('you need to supply a parent to addNode');
  576.   }
  577.  
  578.   var resource;
  579.   if (aId != null) {
  580.     resource = RDFSVC.GetResource(aId);
  581.     /* ensure the node doesn't already exist */
  582.     if (this._dataSource.GetTargets(resource,
  583.         RDFSVC.GetResource(SP+'Hidden'), true).hasMoreElements()) {
  584.       /* the node has a "hidden" attribute so it must exist */
  585.       return null;
  586.     }
  587.   } else {
  588.     /* no id supplied - make it anonymous */
  589.     resource = RDFSVC.GetAnonymousResource();
  590.   }
  591.  
  592.   /* create an arc making the node hidden, thus creating the object in RDF */
  593.   this._dataSource.Assert(resource,
  594.               RDFSVC.GetResource(SP+'Hidden'),
  595.               RDFSVC.GetLiteral('true'), true);
  596.   if (aContainer) {
  597.     RDFCU.MakeSeq(this._dataSource, resource);
  598.   }
  599.  
  600.   /* create the javascript proxy object */
  601.   var node = new ServicePaneNode(this._dataSource, resource);
  602.   DEBUG ('ServicePaneService.addNode: node.hidden='+node.hidden);
  603.  
  604.   /* add the node to the parent */
  605.   aParent.appendChild(node)
  606.  
  607.   /* if the node is a container, make it open */
  608.   if (aContainer) {
  609.     node.isOpen = true;
  610.   }
  611.  
  612.   // by default nothing is editable and everything is very heavy
  613.   node.editable = false;
  614.   node.setAttributeNS(SP, 'Weight', 9999);
  615.   return node;
  616. }
  617. ServicePaneService.prototype.removeNode =
  618. function ServicePaneService_removeNode(aNode) {
  619.   if (!this._initialized) { this.init(); }
  620.   /* ensure the node is supplied*/
  621.   if (aNode == null) {
  622.     throw Ce('you need to supply a node to removeNode');
  623.   }
  624.  
  625.   if (aNode.id == this._root.id) {
  626.     throw Ce("you can't remove the root node");
  627.   }
  628.  
  629.   if(aNode.parentNode) {
  630.     // remove the node from it's parent
  631.     aNode.parentNode.removeChild(aNode);
  632.   } else {
  633.     // or if it's an orphan call the internal function to clear it out
  634.     aNode.clearNode();
  635.   }
  636. }
  637. ServicePaneService.prototype.getNode =
  638. function ServicePaneService_getNode(aId) {
  639.   if (!this._initialized) { this.init(); }
  640.   var resource = RDFSVC.GetResource(aId);
  641.   /* ensure the node already exists */
  642.   if (!this._dataSource.GetTargets(resource,
  643.       RDFSVC.GetResource(SP+'Hidden'), true).hasMoreElements()) {
  644.     /* the node has a "hidden" attribute so it must exist */
  645.     return null;
  646.   }
  647.  
  648.   return new ServicePaneNode(this._dataSource, resource);
  649. }
  650.  
  651. ServicePaneService.prototype.getNodeForURL =
  652. function ServicePaneService_getNodeForURL(aURL) {
  653.   if (!this._initialized) { this.init(); }
  654.  
  655.   var ncURL = RDFSVC.GetResource(NC+"URL");
  656.   var url = RDFSVC.GetLiteral(aURL);
  657.   var target = this._dataSource.GetSource(ncURL, url, true);
  658.   return (target) ? this.getNode(target.Value) : null;
  659. }
  660.  
  661. ServicePaneService.prototype.getNodesByAttributeNS =
  662. function ServicePaneService_getNodesByAttributeNS(aNamespace, aName, aValue) {
  663.   if (!this._initialized) { this.init(); }
  664.  
  665.   var property = RDFSVC.GetResource(aNamespace+aName);
  666.   var target = RDFSVC.GetLiteral(aValue);
  667.   var sourceEnum = this._dataSource.GetSources(property, target, true);
  668.  
  669.   var nodeList = Cc["@songbirdnest.com/moz/xpcom/threadsafe-array;1"].createInstance(Ci.nsIMutableArray);
  670.   while (sourceEnum.hasMoreElements()) {
  671.     var source = sourceEnum.getNext().QueryInterface(Ci.nsIRDFResource);
  672.     if (source) {
  673.       var node = this.getNode(source.Value);
  674.       if (node) {
  675.         nodeList.appendElement(node, false);
  676.       }
  677.     }
  678.   }
  679.  
  680.   return nodeList.QueryInterface(Ci.nsIArray);
  681. }
  682.  
  683. ServicePaneService.prototype.sortNode =
  684. function ServicePaneService_sortNode(aNode) {
  685.   // move node to the end of the service pane.
  686.   this._root.appendChild(aNode);
  687.  
  688.   // sort the node by its weight.
  689.   var value = parseInt(aNode.getAttributeNS(SP, 'Weight'));
  690.   while (aNode.previousSibling) {
  691.     var prev_value =
  692.         parseInt(aNode.previousSibling.getAttributeNS(SP, 'Weight'));
  693.     if (prev_value > value) {
  694.       this._root.insertBefore(aNode, aNode.previousSibling);
  695.     } else {
  696.       break;
  697.     }
  698.   }
  699. }
  700.  
  701. ServicePaneService.prototype.save =
  702. function ServicePaneService_save() {
  703.   /* FIXME: this function should go away */
  704. }
  705.  
  706. ServicePaneService.prototype.fillContextMenu =
  707. function ServicePaneService_fillContextMenu(aId, aContextMenu, aParentWindow) {
  708.   if (!this._initialized) { this.init(); }
  709.   var node = aId ? this.getNode(aId) : null;
  710.   for (var i=0; i<this._modules.length; i++) {
  711.     try {
  712.       this._modules[i].fillContextMenu(node, aContextMenu, aParentWindow);
  713.     } catch (ex) {
  714.       if (gPrefs.getPrefType('javascript.options.showInConsole') &&
  715.           gPrefs.getBoolPref('javascript.options.showInConsole')) {
  716.         Components.utils.reportError(ex);
  717.       }
  718.     }
  719.   }
  720. }
  721.  
  722. ServicePaneService.prototype.fillNewItemMenu =
  723. function ServicePaneService_fillNewItemMenu(aId, aContextMenu, aParentWindow) {
  724.   if (!this._initialized) { this.init(); }
  725.   var node = aId ? this.getNode(aId) : null;
  726.   for (var i=0; i<this._modules.length; i++) {
  727.     try {
  728.       this._modules[i].fillNewItemMenu(node, aContextMenu, aParentWindow);
  729.     } catch (ex) {
  730.       if (gPrefs.getPrefType('javascript.options.showInConsole') &&
  731.           gPrefs.getBoolPref('javascript.options.showInConsole')) {
  732.         Components.utils.reportError(ex);
  733.       }
  734.     }
  735.   }
  736. }
  737.  
  738. ServicePaneService.prototype.onSelectionChanged =
  739. function ServicePaneService_onSelectionChanged(aId, aContainer, aParentWindow) {
  740.   if (!this._initialized) { this.init(); }
  741.   var node = aId ? this.getNode(aId) : null;
  742.   for (var i=0; i<this._modules.length; i++) {
  743.     try {
  744.       this._modules[i].onSelectionChanged(node, aContainer, aParentWindow);
  745.     } catch (ex) {
  746.       if (gPrefs.getPrefType('javascript.options.showInConsole') &&
  747.           gPrefs.getBoolPref('javascript.options.showInConsole')) {
  748.         Components.utils.reportError(ex);
  749.       }
  750.     }
  751.   }
  752. }
  753.  
  754. ServicePaneService.prototype._canDropReorder =
  755. function ServicePaneService__canDropReorder(aNode, aDragSession, aOrientation) {
  756.   if (!this._initialized) { this.init(); }
  757.   // see if we can handle the drag and drop based on node properties
  758.   var types = [];
  759.   if (aOrientation == 0) {
  760.     // drop in
  761.     if (!aNode.isContainer) {
  762.       // not a container - don't even try
  763.       return null;
  764.     }
  765.     if (aNode.dndAcceptIn) {
  766.       types = aNode.dndAcceptIn.split(',');
  767.     }
  768.   } else {
  769.     // drop near
  770.     if (aNode.dndAcceptNear) {
  771.       types = aNode.dndAcceptNear.split(',');
  772.     }
  773.   }
  774.   for (var i=0; i<types.length; i++) {
  775.     if (aDragSession.isDataFlavorSupported(types[i])) {
  776.       return types[i];
  777.     }
  778.   }
  779.   return null;
  780. }
  781. ServicePaneService.prototype.canDrop =
  782. function ServicePaneService_canDrop(aId, aDragSession, aOrientation, aWindow) {
  783.   if (!this._initialized) { this.init(); }
  784.   var node = this.getNode(aId);
  785.   if (!node) {
  786.     return false;
  787.   }
  788.  
  789.   // see if we can handle the drag and drop based on node properties
  790.   if (this._canDropReorder(node, aDragSession, aOrientation)) {
  791.     return true;
  792.   }
  793.  
  794.   // let the module that owns this node handle this
  795.   if (node.contractid && Cc[node.contractid]) {
  796.     var module = Cc[node.contractid].getService(Ci.sbIServicePaneModule);
  797.     if (module) {
  798.       return module.canDrop(node, aDragSession, aOrientation, aWindow);
  799.     }
  800.   }
  801.   return false;
  802. }
  803. ServicePaneService.prototype.onDrop =
  804. function ServicePaneService_onDrop(aId, aDragSession, aOrientation, aWindow) {
  805.   if (!this._initialized) { this.init(); }
  806.   var node = this.getNode(aId);
  807.   if (!node) {
  808.     return;
  809.   }
  810.   // see if this is a reorder we can handle based on node properties
  811.   var type = this._canDropReorder(node, aDragSession, aOrientation);
  812.   if (type) {
  813.     // we're in business
  814.  
  815.     // do the dance to get our data out of the dnd system
  816.     // create an nsITransferable
  817.     var transferable = Components.classes["@mozilla.org/widget/transferable;1"].
  818.         createInstance(Components.interfaces.nsITransferable);
  819.     // specify what kind of data we want it to contain
  820.     transferable.addDataFlavor(type);
  821.     // ask the drag session to fill the transferable with that data
  822.     aDragSession.getData(transferable, 0);
  823.     // get the data from the transferable
  824.     var data = {};
  825.     var dataLength = {};
  826.     transferable.getTransferData(type, data, dataLength);
  827.     // it's always a string. always.
  828.     data = data.value.QueryInterface(Components.interfaces.nsISupportsString);
  829.     data = data.toString();
  830.  
  831.     // for drag and drop reordering the data is just the servicepane uri
  832.     var droppedNode = this.getNode(data);
  833.  
  834.     // fail if we can't get the node
  835.     if (!droppedNode) {
  836.       return;
  837.     }
  838.  
  839.     if (aOrientation == 0) {
  840.       // drop into
  841.       // we should append the new node to the current node
  842.       node.appendChild(droppedNode);
  843.     } else if (aOrientation > 0) {
  844.       // drop after
  845.       if (node.nextSibling) {
  846.         // not the last node
  847.         if (droppedNode.id == node.nextSibling.id) {
  848.           return; // can't insert after ourselves
  849.         }
  850.         node.parentNode.insertBefore(droppedNode, node.nextSibling);
  851.       } else {
  852.         // the last node
  853.         node.parentNode.appendChild(droppedNode);
  854.       }
  855.     } else {
  856.       // drop before
  857.       if (droppedNode.id == node.id) {
  858.         return; // can't insert before ourselves
  859.       }
  860.       node.parentNode.insertBefore(droppedNode, node);
  861.     }
  862.     return;
  863.   }
  864.  
  865.   // or let the module that owns this node handle it
  866.   if (node.contractid) {
  867.     var module = Cc[node.contractid].getService(Ci.sbIServicePaneModule);
  868.     if (module) {
  869.       module.onDrop(node, aDragSession, aOrientation, aWindow);
  870.     }
  871.   }
  872. }
  873. ServicePaneService.prototype.onDragGesture =
  874. function ServicePaneService_onDragGesture(aId, aTransferable) {
  875.   if (!this._initialized) { this.init(); }
  876.   DEBUG('onDragGesture('+aId+', '+aTransferable+')');
  877.   var node = this.getNode(aId);
  878.   if (!node) {
  879.     return false;
  880.   }
  881.  
  882.   var success = false;
  883.  
  884.   // create a transferable
  885.   var transferable = Components.classes["@mozilla.org/widget/transferable;1"].
  886.     createInstance(Components.interfaces.nsITransferable);
  887.  
  888.   // get drag types from the node data
  889.   if (node.dndDragTypes) {
  890.     var types = node.dndDragTypes.split(',');
  891.     for (var i=0; i<types.length; i++) {
  892.       var type = types[i];
  893.       transferable.addDataFlavor(type);
  894.       var text = Components.classes["@mozilla.org/supports-string;1"].
  895.          createInstance(Components.interfaces.nsISupportsString);
  896.       text.data = node.id;
  897.       // double the length - it's unicode - this is stupid
  898.       transferable.setTransferData(type, text, text.data.length*2);
  899.       success = true;
  900.     }
  901.   }
  902.  
  903.   if (node.contractid && Cc[node.contractid]) {
  904.     var module = Cc[node.contractid].getService(Ci.sbIServicePaneModule);
  905.     if (module) {
  906.       if (module.onDragGesture(node, transferable)) {
  907.         if (!success) {
  908.           success = true;
  909.         }
  910.       }
  911.     }
  912.   }
  913.  
  914.   if (success) {
  915.     aTransferable.value = transferable;
  916.   }
  917.  
  918.   DEBUG(' success='+success);
  919.  
  920.   return success;
  921. }
  922.  
  923. /**
  924.  * Called when a node is renamed by the user.
  925.  * Delegates to the module that owns the given node.
  926.  */
  927. ServicePaneService.prototype.onRename =
  928. function ServicePaneService_onRename(aID, aNewName) {
  929.   if (!this._initialized) { this.init(); }
  930.   var node = this.getNode(aID);
  931.   if (!node || !node.editable) {
  932.     return;
  933.   }
  934.  
  935.   // Pass the message on to the node owner
  936.   if (node.contractid) {
  937.     var module = Cc[node.contractid].getService(Ci.sbIServicePaneModule);
  938.     if (module) {
  939.       module.onRename(node, aNewName);
  940.     }
  941.   }
  942. }
  943.  
  944. ServicePaneService.prototype.addListener =
  945. function ServicePaneService_addListener(aListener) {
  946.   this._listeners.push(aListener);
  947. }
  948.  
  949. ServicePaneService.prototype.removeListener =
  950. function ServicePaneService_removeListener(aListener) {
  951.   for (var i=0; i<this._listeners.length; i++) {
  952.     if (this._listeners[i] == aListener) {
  953.       this._listeners.splice(i,1);
  954.     }
  955.   }
  956. }
  957.  
  958. /* nsIRDFObserver -> sbIServicePaneListener */
  959. ServicePaneService.prototype._notifyListeners =
  960. function ServicePaneService__notifyListeners(aSource, aProperty) {
  961.   DEBUG('_notifyListeners aSource='+aSource.Value+' aProperty='+aProperty.Value);
  962.   var nodeid = aSource.Value;
  963.   var property = aProperty.Value;
  964.   for (var i=0; i<this._listeners.length; i++) {
  965.     this._listeners[i].nodePropertyChanged(nodeid, property);
  966.   }
  967. }
  968.  
  969. ServicePaneService.prototype.onAssert =
  970. function ServicePaneService_onAssert(aDataSource, aSource, aProperty, aTarget) {
  971.   this._notifyListeners(aSource, aProperty);
  972. },
  973. ServicePaneService.prototype.onUnassert =
  974. function ServicePaneService_onUnassert(aDataSource, aSource, aProperty, aTarget) {
  975.   this._notifyListeners(aSource, aProperty);
  976. },
  977. ServicePaneService.prototype.onChange =
  978. function ServicePaneService_onChange(aDataSource, aSource, aProperty, aOldTarget, aNewTarget) {
  979.   this._notifyListeners(aSource, aProperty);
  980. },
  981. ServicePaneService.prototype.onMove =
  982. function ServicePaneService_onMove(aDataSource, aOldSource, aNewSource, aProperty, aTarget) {
  983.   this._notifyListeners(aOldSource, aProperty);
  984.   this._notifyListeners(aNewSource, aProperty);
  985. },
  986. ServicePaneService.prototype.onBeginUpdateBatch =
  987. function ServicePaneService_onBeginUpdateBatch(a) { },
  988. ServicePaneService.prototype.onEndUpdateBatch =
  989. function ServicePaneService_onEndUpdateBatch(a) { }
  990.  
  991.  
  992. /* this is a wrapper for the nsIRDFDataSource that will translate some of the
  993.   properties via stringbundles */
  994. function dsTranslator(inner, sps) {
  995.   this.inner = inner;
  996.   this.sps = sps;
  997.   this.properties = {};
  998.   this.properties[NC+'Name'] = true;
  999.   this.stringBundleService = Cc['@mozilla.org/intl/stringbundle;1']
  1000.       .getService(Ci.nsIStringBundleService);
  1001. }
  1002. dsTranslator.prototype = {
  1003.   // impl methods
  1004.  
  1005.   // Try to translate a key based on a string bundle uri. Either return an
  1006.   // nsIRDFLiteral on success or null on failure.
  1007.   translateKey: function translateKey(stringbundleuri, key) {
  1008.     var bundle = this.stringBundleService.createBundle(stringbundleuri);
  1009.     if (!bundle) { return null; }
  1010.     var message = null;
  1011.     try {
  1012.       message = bundle.GetStringFromName(key);
  1013.     } catch (e) { }
  1014.     if (!message) { return null; }
  1015.     return RDFSVC.GetLiteral(message);
  1016.   },
  1017.  
  1018.   // Try to translate an RDF triple. Either return either the original or
  1019.   // the translated target
  1020.   translateTriple: function translateTriple(aSource, aProperty, aTarget) {
  1021.     // if there's no target, fail
  1022.     if (!aTarget) { return null; }
  1023.  
  1024.     // if the target is not an nsIRDFLiteral, return it
  1025.     if (!(aTarget instanceof Ci.nsIRDFLiteral)) { return aTarget; }
  1026.  
  1027.     // if the target does not begin with "&", return it
  1028.     if (aTarget.Value.length < 1 ||
  1029.         aTarget.Value[0] != '&') {
  1030.       return aTarget;
  1031.     }
  1032.  
  1033.     // the key is the rest of aTarget.Value
  1034.     var key = aTarget.Value.substring(1);
  1035.  
  1036.     // we need to find the string bundle we look in these places in this
  1037.     // order:
  1038.     //   a property on the node (http://songbirdnest.com/rdf/servicepane#stringbundle)
  1039.     //   the .stringbundle attribute on the associated module service
  1040.     //   the default stringbundle on the service pane service
  1041.  
  1042.     // FIXME: after testing wrap each of these steps in try/catch
  1043.  
  1044.     var node = this.sps.getNode(aSource.Value);
  1045.  
  1046.     // the node's stringbundle
  1047.     if (node && node.stringbundle) {
  1048.       var translated = this.translateKey(node.stringbundle, key);
  1049.       if (translated) {
  1050.         return translated;
  1051.       }
  1052.     }
  1053.  
  1054.     // the module's stringbundle
  1055.     if (node && node.contractid && Cc[node.contractid]) {
  1056.       var m = Cc[node.contractid].getService(Ci.sbIServicePaneModule);
  1057.       if (m && m.stringbundle) {
  1058.         var translated = this.translateKey(m.stringbundle, key);
  1059.         if (translated) {
  1060.           return translated
  1061.         }
  1062.       }
  1063.     }
  1064.  
  1065.     // the app's default stringbundle
  1066.     var translated = this.translateKey(STRINGBUNDLE, key)
  1067.     if (translated) {
  1068.       return translated;
  1069.     }
  1070.  
  1071.     // couldn't translate it - let's just return the raw target
  1072.     return aTarget;
  1073.   },
  1074.  
  1075.   // nsISupports
  1076.   QueryInterface: function QueryInterface(iid) {
  1077.     return this.inner.QueryInterface(iid);
  1078.   },
  1079.  
  1080.   // nsIRDFDataSource
  1081.   get URI() { return this.inner.URI; },
  1082.   GetSource: function GetSource(a,b,c) { return this.inner.GetSource(a,b,c); },
  1083.   GetSources: function GetSources(a,b,c) { return this.inner.GetSources(a,b,c); },
  1084.   GetTarget: function GetTarget(aSource, aProperty, aTruthValue) {
  1085.     var target = this.inner.GetTarget(aSource, aProperty, aTruthValue);
  1086.  
  1087.     // is this property translatable
  1088.     if (this.properties[aProperty.Value]) {
  1089.       return this.translateTriple(aSource, aProperty, target);
  1090.     } else {
  1091.       return target;
  1092.     }
  1093.   },
  1094.   // FIXME do we need to translate GetTargets too?
  1095.   GetTargets: function GetTargets(aSource, aProperty, aTruthValue) {
  1096.     var targets = this.inner.GetTargets(aSource, aProperty, aTruthValue);
  1097.  
  1098.     // is this property translatable?
  1099.     if (this.properties[aProperty.Value]) {
  1100.       // we need to create a proxy enumerator object
  1101.       var self = this;
  1102.       var enumerator = {
  1103.         hasMoreElements: function hasMoreElements() {
  1104.           return targets.hasMoreElements();
  1105.         },
  1106.         getNext: function getNext() {
  1107.           return self.translateTriple(aSource, aProperty,
  1108.               targets.getNext());
  1109.         },
  1110.         QueryInterface: function QueryInterface(iid) {
  1111.           return targets.QueryInterface(iid);
  1112.         }
  1113.       };
  1114.       return enumerator
  1115.     } else {
  1116.       return targets;
  1117.     }
  1118.   },
  1119.   Assert: function Assert(a,b,c,d) { this.inner.Assert(a,b,c,d); },
  1120.   Unassert: function Unassert(a,b,c) { this.inner.Unassert(a,b,c); },
  1121.   Change: function Change(a,b,c,d) { this.inner.Change(a,b,c,d); },
  1122.   Move: function Move(a,b,c,d) { this.inner.Move(a,b,c,d); },
  1123.   HasAssertion: function HasAssertion(a,b,c,d) { return this.inner.HasAssertion(a,b,c,d); },
  1124.   AddObserver: function AddObserver(a) { this.inner.AddObserver(a); },
  1125.   RemoveObserver: function RemoveObserver(a) { this.inner.RemoveObserver(a); },
  1126.   ArcLabelsIn: function ArcLabelsIn(a) { return this.inner.ArcLabelsIn(a); },
  1127.   ArcLabelsOut: function ArcLabelsOut(a) { return this.inner.ArcLabelsOut(a); },
  1128.   GetAllResources: function GetAllResources() { return this.inner.GetAllResourvces(); },
  1129.   IsCommandEnabled: function IsCommandEnabled(a,b,c) { return this.inner.IsCommandEnabled(a,b,c); },
  1130.   DoCommand: function DoCommand(a,b,c) { this.inner.DoCommand(a,b,c); },
  1131.   IsCommandEnabled: function IsCommandEnabled(a,b,c) { return this.inner.IsCommandEnabled(a,b,c); },
  1132.   GetAllCmds: function GetAllCmds() { return this.inner.GetAllCmds(); },
  1133.   hasArcIn: function hasArcIn(a,b) { this.inner.hasArcIn(a,b); },
  1134.   hasArcOut: function hasArcOut(a,b) { this.inner.hasArcOut(a,b); },
  1135.   beginUpdateBatch: function beginUpdateBatch() { this.inner.beginUpdateBatch(); },
  1136.   endUpdateBatch: function endUpdateBatch() { this.inner.endUpdateBatch(); },
  1137.  
  1138.   // nsIRDFRemoteDataSource
  1139.   get loaded() { return this.inner.loaded; },
  1140.   Init: function Init(a) { this.inner.Init(a); },
  1141.   Refresh: function Refresh(a) { this.inner.Refresh(a); },
  1142.   Flush: function Flush() { this.inner.Flush(); },
  1143.   FlushTo: function FlushTo(a) { this.inner.FlushTo(a); }
  1144. };
  1145.  
  1146.  
  1147. /* an RDF observer that handles automatic saving of RDF state */
  1148. function dsSaver(ds, frequency) {
  1149.   this.ds = ds.QueryInterface(Ci.nsIRDFDataSource);
  1150.   this.ds.QueryInterface(Ci.nsIRDFRemoteDataSource);
  1151.  
  1152.   this.dirty = false; // has the DS changed since it was last saved?
  1153.  
  1154.   this.timer = Cc['@mozilla.org/timer;1'].createInstance(Ci.nsITimer);
  1155.   this.timer.init(this, frequency, Ci.nsITimer.TYPE_REPEATING_SLACK);
  1156.  
  1157.   Cc['@mozilla.org/observer-service;1'].getService(Ci.nsIObserverService)
  1158.       .addObserver(this, 'quit-application', false);
  1159.  
  1160.   this.ds.AddObserver(this);
  1161. }
  1162. dsSaver.prototype = {
  1163.   save: function dsSaver_save() {
  1164.     if (this.dirty) {
  1165.       this.ds.Flush();
  1166.       this.dirty = false;
  1167.     }
  1168.   },
  1169.   /* nsISupports */
  1170.   QueryInterface: function dsSaver_QueryInterface(iid) {
  1171.     if (!iid.equals(Ci.nsIRDFObserver) &&
  1172.         !iid.equals(Ci.nsIObserver) &&
  1173.         !iid.equals(Ci.nsISupports)) {
  1174.       throw Components.results.NS_ERROR_NO_INTERFACE;
  1175.     }
  1176.     return this;
  1177.   },
  1178.  
  1179.   /* nsIObserver */
  1180.   observe: function dsSaver_observe(subject, topic, data) {
  1181.     switch (topic) {
  1182.       case 'timer-callback':
  1183.         if (subject == this.timer) {
  1184.           this.save();
  1185.         }
  1186.         break;
  1187.       case 'quit-application':
  1188.         this.save();
  1189.         Cc['@mozilla.org/observer-service;1'].getService(Ci.nsIObserverService)
  1190.           .removeObserver(this, 'quit-application');
  1191.         this.ds.RemoveObserver(this);
  1192.         this.timer.cancel();
  1193.         this.timer = null;
  1194.         break;
  1195.       default:
  1196.     }
  1197.   },
  1198.  
  1199.   /* nsIRDFObserver */
  1200.   onAssert: function dsSaver_onAssert(a, b, c, d) {
  1201.     this.dirty = true;
  1202.   },
  1203.   onUnassert: function dsSaver_onUnassert(a, b, c, d) {
  1204.     this.dirty = true;
  1205.   },
  1206.   onChange: function dsSaver_onChange(a, b, c, d, e) {
  1207.     this.dirty = true;
  1208.   },
  1209.   onMove: function dsSaver_onMove(a, b, c, d, e) {
  1210.     this.dirty = true;
  1211.   },
  1212.   onBeginUpdateBatch: function dsSaver_onBeginUpdateBatch(a) { },
  1213.   onEndUpdateBatch: function dsSaver_onEndUpdateBatch(a) { }
  1214. }
  1215.  
  1216. /**
  1217.  * /brief XPCOM initialization code
  1218.  */
  1219. function makeGetModule(CONSTRUCTOR, CID, CLASSNAME, CONTRACTID, CATEGORIES) {
  1220.   return function (comMgr, fileSpec) {
  1221.     return {
  1222.       registerSelf : function (compMgr, fileSpec, location, type) {
  1223.         compMgr.QueryInterface(Ci.nsIComponentRegistrar);
  1224.         compMgr.registerFactoryLocation(CID,
  1225.                         CLASSNAME,
  1226.                         CONTRACTID,
  1227.                         fileSpec,
  1228.                         location,
  1229.                         type);
  1230.         if (CATEGORIES && CATEGORIES.length) {
  1231.           var catman =  Cc["@mozilla.org/categorymanager;1"]
  1232.               .getService(Ci.nsICategoryManager);
  1233.           for (var i=0; i<CATEGORIES.length; i++) {
  1234.             var e = CATEGORIES[i];
  1235.             catman.addCategoryEntry(e.category, e.entry, e.value,
  1236.               true, true);
  1237.           }
  1238.         }
  1239.       },
  1240.  
  1241.       getClassObject : function (compMgr, cid, iid) {
  1242.         if (!cid.equals(CID)) {
  1243.           throw Cr.NS_ERROR_NO_INTERFACE;
  1244.         }
  1245.  
  1246.         if (!iid.equals(Ci.nsIFactory)) {
  1247.           throw Cr.NS_ERROR_NOT_IMPLEMENTED;
  1248.         }
  1249.  
  1250.         return this._factory;
  1251.       },
  1252.  
  1253.       _factory : {
  1254.         createInstance : function (outer, iid) {
  1255.           if (outer != null) {
  1256.             throw Cr.NS_ERROR_NO_AGGREGATION;
  1257.           }
  1258.           return (new CONSTRUCTOR()).QueryInterface(iid);
  1259.         }
  1260.       },
  1261.  
  1262.       unregisterSelf : function (compMgr, location, type) {
  1263.         compMgr.QueryInterface(Ci.nsIComponentRegistrar);
  1264.         compMgr.unregisterFactoryLocation(CID, location);
  1265.         if (CATEGORIES && CATEGORIES.length) {
  1266.           var catman =  Cc["@mozilla.org/categorymanager;1"]
  1267.               .getService(Ci.nsICategoryManager);
  1268.           for (var i=0; i<CATEGORIES.length; i++) {
  1269.             var e = CATEGORIES[i];
  1270.             catman.deleteCategoryEntry(e.category, e.entry, true);
  1271.           }
  1272.         }
  1273.       },
  1274.  
  1275.       canUnload : function (compMgr) {
  1276.         return true;
  1277.       },
  1278.  
  1279.       QueryInterface : function (iid) {
  1280.         if ( !iid.equals(Ci.nsIModule) ||
  1281.              !iid.equals(Ci.nsISupports) )
  1282.           throw Cr.NS_ERROR_NO_INTERFACE;
  1283.         return this;
  1284.       }
  1285.  
  1286.     };
  1287.   }
  1288. }
  1289.  
  1290. var NSGetModule = makeGetModule (
  1291.   ServicePaneService,
  1292.   Components.ID("{eb5c665a-bfe2-49f1-a747-cd3554e55606}"),
  1293.   "Songbird Service Pane Service",
  1294.   "@songbirdnest.com/servicepane/service;1",
  1295.   [{
  1296.     category: 'app-startup',
  1297.     entry: 'service-pane-service',
  1298.     value: 'service,@songbirdnest.com/servicepane/service;1'
  1299.   }]);
  1300.